﻿#region Copyright information
// 
// Copyright © 2005-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the Biological Object Library and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: CytoBand.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@gmail.com
// - Date Created: 2013-01-25 3:25 PM
// - Last Modified: 2013-01-25 3:59 PM
// 
#endregion
using System;
using System.Collections.Generic;
using System.IO;
using System.Text.RegularExpressions;
using System.Runtime.Serialization;

namespace BOL.Models.Genome
{
    /// <summary>
    /// Describes the positions of cytogenetic bands within a chromosome.
    /// </summary>
    [DataContract]
    public class CytoBand : GenomicSegment, IComparable, IComparable<CytoBand>, IEquatable<CytoBand>
    {
        #region GiemsaStainResult enum

        [DataContract]
        public enum GiemsaStainResult
        {
            [EnumMember]
            Acen,
            [EnumMember]
            GNeg,
            [EnumMember]
            GPos25,
            [EnumMember]
            GPos50,
            [EnumMember]
            GPos75,
            [EnumMember]
            GPos100,
            [EnumMember]
            GVal,
            [EnumMember]
            Stalk,
            [EnumMember]
            Unknown
        }

        #endregion

        #region Private variables

        private Locus _locus;
        private GiemsaStainResult _gieStain;

        #endregion

        #region Public properties

        /// <summary>Gets or sets locus.</summary>
        [DataMember]
        public Locus Locus { get { return _locus; } set { _locus = value; } }

        /// <summary>Gets or sets Giemsa stain results.</summary>
        [DataMember]
        public GiemsaStainResult GieStain { get { return _gieStain; } set { _gieStain = value; } }

        #endregion

        #region Constructors

        public CytoBand(Chromosome chromosome, int from, int to, string locusStr, GiemsaStainResult gieStain) :
            base(chromosome, from, to)
        {
            Locus.TryParse(locusStr, out _locus);
            _gieStain = gieStain;
        }

        public CytoBand(Chromosome chromosome, int from, int to, string locusStr, string gieStainStr) :
            this(chromosome, from, to, locusStr, GetGieStain(gieStainStr)) { }

        public CytoBand(Chromosome chromosome, int from, int to, string locusStr) : this(chromosome, from, to, locusStr, String.Empty) { }

        public CytoBand(Chromosome chromosome, string locusStr) : this(chromosome, -1, -1, locusStr, String.Empty) { }

        //internal CytoBand() : this(new Chromosome(), String.Empty) { }

        #endregion

        #region Protected methods

        private static GiemsaStainResult GetGieStain(string gieStainStr)
        {
            switch (gieStainStr)
            {
                case "acen":
                    return GiemsaStainResult.Acen;
                case "gneg":
                    return GiemsaStainResult.GNeg;
                case "gpos25":
                    return GiemsaStainResult.GPos25;
                case "gpos50":
                    return GiemsaStainResult.GPos50;
                case "gpos75":
                    return GiemsaStainResult.GPos75;
                case "gpos100":
                    return GiemsaStainResult.GPos100;
                case "gval":
                    return GiemsaStainResult.GVal;
                case "stalk":
                    return GiemsaStainResult.Stalk;
                default:
                    return GiemsaStainResult.Unknown;
            }
        }

        private static IList<CytoBand> FromBytes(byte[] buffer)
        {
            var ms = new MemoryStream(buffer);
            return FromStream(ms);
        }

        private static IList<CytoBand> FromFile(string path)
        {
            var fs = new FileStream(path, FileMode.Open);
            return FromStream(fs);
        }

        private static IList<CytoBand> FromStream(Stream stream)
        {
            if (stream == null)
                throw new ArgumentNullException("stream");

            var cytoBands = new List<CytoBand>();
            var sr = new StreamReader(stream);

            while (!sr.EndOfStream)
            {
                var line = sr.ReadLine().Trim();
                var tmp = line.Split("\t".ToCharArray(), StringSplitOptions.RemoveEmptyEntries);
                var chromosome = Chromosome.Parse(tmp[0]);
                var from = Int32.Parse(tmp[1]);
                var to = Int32.Parse(tmp[2]);
                cytoBands.Add(new CytoBand(chromosome, from, to, tmp[3], tmp[4]));
            }

            sr.Close();
            stream.Close();

            return cytoBands;
        }

        #endregion

        #region Conversion methods

        public new static CytoBand Parse(string cytoBand)
        {
            var match = Regex.Match(cytoBand, @"(chr)?(?<chrName>[0-9xyXY]+(cen|ter)?[+-]?)?:?(?<name>[pq0-9.]+)\s?");
            var group = match.Groups;

            var chrName = group["chrName"].Value.ToUpper();
            var name = group["name"].Value;

            return new CytoBand(new Chromosome(chrName), name);
        }

        public static bool TryParse(string cytoBand, out CytoBand result)
        {
            result = null;

            try
            {
                result = Parse(cytoBand);
            }
            catch
            {
                return false;
            }

            return true;
        }

        #endregion

        #region IComparable, IComparable<CytoBand>, IEquatable<CytoBand> implementation

        public new int CompareTo(object other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (!(other is CytoBand))
                throw new InvalidCastException("The 'other' argument is not a CytoBand object.");

            return CompareTo(other as CytoBand);
        }

        public int CompareTo(CytoBand other)
        {
            int compare = Chromosome.CompareTo(other.Chromosome);

            if (compare == 0)
                compare = _locus.CompareTo(other._locus);

            return compare;
        }

        public bool Equals(CytoBand other)
        {
            return base.Equals(other) & _locus.Equals(other._locus) & _gieStain.Equals(other.GieStain);
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            return base.GetHashCode() ^ Locus.GetHashCode() ^ GieStain.GetHashCode();
        }

        public override bool Equals(object other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (!(other is CytoBand))
                throw new InvalidCastException("The 'other' argument is not a CytoBand object.");
            
            return Equals(other as CytoBand);
        }

        public override string ToString()
        {
            return String.Format("{0}{1}", Chromosome, _locus);
        }

        #endregion
    }
}
